// +build linux

package io_uring

import (
	"fmt"
	"runtime"
	"syscall"

	iouring_syscall "gitee.com/childewang/iouring-go/syscall"
)

// IOUring ...
type IOUring struct {
	params *iouring_syscall.IOURingParams
	sq     *SubmissionQueue
	cq     *CompletionQueue
	Flags  uint
	ringFd int

	features uint
	pad      [3]uint
}

// New 新建IoUring
func New(entries uint, params *iouring_syscall.IOURingParams) (*IOUring, error) {
	iouring := &IOUring{
		params: params,
		// userDatas: make(map[uint64]*UserData),
		// cqeSign:   make(chan struct{}, 1),
		// closer:    make(chan struct{}),
		// closed:    make(chan struct{}),
	}

	var err error
	iouring.ringFd, err = iouring_syscall.IOURingSetup(entries, iouring.params)
	if err != nil {
		return nil, err
	}

	fmt.Println("io_uring fd: ", iouring.ringFd)
	if err = mmapIOURing(iouring); err != nil {
		munmapIOURing(iouring)
		return nil, err
	}

	// iour.fileRegister = &fileRegister{
	// 	iouringFd:    iour.fd,
	// 	sparseIndexs: make(map[int]int),
	// }
	// iour.Flags = iour.params.Flags
	// iour.Features = iour.params.Features

	// if err := iour.registerEventfd(); err != nil {
	// 	iour.Close()
	// 	return nil, err
	// }

	// if err := registerIOURing(iour); err != nil {
	// 	iour.Close()
	// 	return nil, err
	// }

	go iouring.run()
	return iouring, nil
}

// GetSQE ...
func (iouring *IOUring) GetSQE() *iouring_syscall.IOUringSQE {
	sqe := iouring.sq.getSQE()
	if sqe != nil {
		return sqe
	}
	return nil
}

// GetCQE ...
func (iouring *IOUring) GetCQE(wait bool) (cqe *iouring_syscall.IOUringCQE, err error) {
	var tryPeeks int
	// fmt.Printf("cq: %+v\n", iouring.cq)
	for {
		// fmt.Println("get cqe")
		cqe = iouring.cq.peek()
		if cqe != nil {
			// fmt.Println("cq advance")
			iouring.cq.advance(1)
			return
		}

		if !wait && !iouring.sq.cqOverflow() {
			err = syscall.EAGAIN
			fmt.Println("err1: ", err)
			return
		}

		if iouring.sq.cqOverflow() {
			fmt.Println("cq overflow")
			_, err = iouring_syscall.IOURingEnter(uint(iouring.ringFd), 0, 0, iouring_syscall.IORING_ENTER_FLAGS_GETEVENTS, nil)
			if err != nil {
				fmt.Println("err2: ", err)
				return
			}
			continue
		}

		if tryPeeks++; tryPeeks < 3 {
			runtime.Gosched()
			continue
		}

		// select {
		// case <-iouring.cqeSign:
		// case <-iouring.closer:
		// 	return nil, ErrIOURingClosed
		// }
	}
}

func (iouring *IOUring) needEnter(flags *uint32) bool {
	if (uint32(iouring.Flags) & iouring_syscall.IORING_SETUP_SQPOLL) == 0 {
		return true
	}

	if iouring.sq.needWakeup() {
		*flags |= iouring_syscall.IORING_SQ_NEED_WAKEUP
		return true
	}
	return false
}

// Submit ...
func (iouring *IOUring) Submit() (submitted int, err error) {
	fmt.Printf("Submit: %+v\n", *iouring.sq)
	submitted = iouring.sq.flush()

	var flags uint32

	if !iouring.needEnter(&flags) || submitted == 0 {
		fmt.Println("submit needEnter............................................")
		return
	}

	if (uint32(iouring.Flags) & iouring_syscall.IORING_SETUP_IOPOLL) != 0 {
		flags |= iouring_syscall.IORING_ENTER_FLAGS_GETEVENTS
	}

	submitted, err = iouring_syscall.IOURingEnter(uint(iouring.ringFd), uint32(submitted), 1, flags, nil)
	return
}

// SetIOPoll ...
func (iouring *IOUring) SetIOPoll() {
	iouring.params.Flags = iouring_syscall.IORING_SETUP_IOPOLL
}

// SetSQEData ...
func (iouring *IOUring) SetSQEData(data uint64) {
}

// GetCQEData
func (iouring *IOUring) GetCQEData() {}

func (iouring *IOUring) run() {
	for {
		// cqe, err := iouring.getCQE(true)
		// if cqe == nil || err != nil {
		// 	if err == ErrIOURingClosed {
		// 		// close(iouring.closed)
		// 		return
		// 	}
		// 	log.Println("runComplete error: ", err)
		// 	continue
		// }

		// // log.Println("cqe user data", (cqe.UserData))

		// iouring.userDataLock.Lock()
		// userData := iouring.userDatas[cqe.UserData]
		// if userData == nil {
		// 	iouring.userDataLock.Unlock()
		// 	log.Println("runComplete: notfound user data ", uintptr(cqe.UserData))
		// 	continue
		// }
		// delete(iouring.userDatas, cqe.UserData)
		// iouring.userDataLock.Unlock()

		// userData.request.complate(cqe)

		// // ignore link timeout
		// if userData.opcode == iouring_syscall.IORING_OP_LINK_TIMEOUT {
		// 	continue
		// }

		// if userData.resulter != nil {
		// 	userData.resulter <- userData.request
		// }
	}
}

// Close IOURing
func (iour *IOUring) Close() error {
	// iour.submitLock.Lock()
	// defer iour.submitLock.Unlock()

	// select {
	// case <-iour.closer:
	// default:
	// 	close(iour.closer)
	// }

	// if iour.eventfd > 0 {
	// 	if err := removeIOURing(iour); err != nil {
	// 		return err
	// 	}
	// 	syscall.Close(iour.eventfd)
	// 	iour.eventfd = -1
	// }

	// <-iour.closed

	// if err := munmapIOURing(iour); err != nil {
	// 	return err
	// }

	// if !iour.fdclosed {
	// 	if err := syscall.Close(iour.fd); err != nil {
	// 		return os.NewSyscallError("close", err)
	// 	}
	// 	iour.fdclosed = true
	// }

	return nil
}
